package rootnode

import (
	"encoding/binary"
	"errors"
	"io"
	"net"
	"strconv"
)

const ProtoMagic uint8 = 0xF1

const (
	OpError uint8 = 0xFF
	OpOK          = 0x00

	CreateAccount           = 0x01
	DeleteAccount           = 0x02
	CreateContainer         = 0x03
	DeleteContainer         = 0x05
	ListPartitions          = 0x07
	ReportPartitionsFailure = 0x08
)

const (
	//the packet header size
	HeaderLength = 20
	//the reserved argument count
	MaxArgCount = 4
)

//the split token within internal keys
const SplitString string = "/"

//the message structure definition
type Packet struct {
	//header
	Magic     uint8
	Opcode    uint8
	Argnum    uint8
	Reserved  uint8 //0x00
	Arglength [MaxArgCount]uint32

	//body
	Args [MaxArgCount][]byte
}

func NewPacket() *Packet {
	pkt := new(Packet)
	pkt.Magic = ProtoMagic
	return pkt
}

func (p *Packet) marshalHeader() (out []byte) {
	out = make([]byte, HeaderLength)
	out[0] = p.Magic
	out[1] = p.Opcode
	out[2] = p.Argnum
	out[3] = p.Reserved

	//argument size in little endian
	for i, arg := range p.Arglength {
		n := 4 + 4*i
		binary.LittleEndian.PutUint32(out[n:n+4], arg)
	}

	return out
}

func (p *Packet) unmarshalHeader(in []byte) error {
	p.Magic = in[0]
	if p.Magic != ProtoMagic {
		return errors.New("Bad Magic " + strconv.Itoa(int(p.Magic)))
	}

	p.Opcode = in[1]
	p.Argnum = in[2]

	//little endian
	for i := range p.Arglength {
		n := 4 + 4*i
		p.Arglength[i] = binary.LittleEndian.Uint32(in[n : n+4])
	}

	return nil
}

func (p *Packet) WriteToConn(c net.Conn) error {
	//firstly the header
	hdrBuf := p.marshalHeader()
	_, err := c.Write(hdrBuf)
	if err != nil {
		return err
	}

	//then the body
	for i := 0; i < int(p.Argnum); i++ {
		_, err = c.Write(p.Args[i])

		if err != nil {
			break
		}
	}

	return err
}

func (p *Packet) ReadFromConn(c net.Conn) error {
	hdrBuf := make([]byte, HeaderLength)
	_, err := io.ReadFull(c, hdrBuf)
	if err != nil {
		return err
	}

	err = p.unmarshalHeader(hdrBuf)
	if err != nil {
		return err
	}

	for i := 0; i < int(p.Argnum); i++ {
		p.Args[i] = make([]byte, p.Arglength[i])
		_, err := io.ReadFull(c, p.Args[i])

		if err != nil {
			return err
		}
	}
	return nil
}

func (p *Packet) PackErrorReply(msg string) {
	p.Opcode = OpError
	p.Argnum = 1
	p.Arglength[0] = uint32(len([]byte(msg)))
	p.Args[0] = []byte(msg)
}
